<script setup lang="ts">
import { ref, computed, onMounted, onUpdated, nextTick } from 'vue';

const props = defineProps({
    items: {
        type: Array,
        required: true,
    },
    itemHeight: {
        type: Number,
        default: 50,
    },
    buffer: {
        type: Number,
        default: 5,
    },
});

const container: any = ref(null); // 容器元素
const scrollTop: any = ref(0); // 当前滚动位置
const itemHeights: any = ref([]); // 存储每个列表项的高度
const visibleCount: any = ref(10); // 可见区域显示的列表项数量

// 列表总高度
const totalHeight = computed(() => {
    return props.items.reduce((sum, _, index) => sum + getItemHeight(index), 0);
});

// 获取列表项的高度
const getItemHeight = (index: number) => {
    return itemHeights.value[index] || props.itemHeight;
};

// 当前可见区域的起始索引
const startIndex = computed(() => Math.max(0, findStartIndex(scrollTop.value) - props.buffer));

// 当前可见区域的结束索引
const endIndex = computed(() => Math.min(
    props.items.length,
    startIndex.value + visibleCount.value + props.buffer * 2
));

// 当前可见的列表项
const visibleItems = computed(() => props.items
    .slice(startIndex.value, endIndex.value)
    .map((item: any, index) => {
        const realIndex = startIndex.value + index
        const offset = calculateOffset(realIndex);
        return {
            index: realIndex,
            id: item.id || realIndex,
            data: item,
            offset,
        };
    }));

// 计算列表项的偏移量
const calculateOffset = (index: number | undefined) => {
    return props.items
        .slice(0, index)
        .reduce((sum, _, i) => sum + getItemHeight(i), 0);
};

// 查找起始索引（二分查找优化）
const findStartIndex = (scrollTop: number) => {
    let low = 0;
    let high = props.items.length - 1;
    while (low <= high) {
        const mid = Math.floor((low + high) / 2);
        const offset: any = calculateOffset(mid);
        if (offset <= scrollTop) {
            low = mid + 1;
        } else {
            high = mid - 1;
        }
    }
    return high;
};

// 处理滚动事件（使用 requestAnimationFrame 优化性能）
const handleScroll = () => {
    requestAnimationFrame(() => {
        scrollTop.value = container.value?.scrollTop;
    });
};

// 更新列表项高度
const updateItemHeights = () => {
    const items = container.value.querySelectorAll('.virtual-list-item');
    items.forEach((item: any, index: number) => {
        const realIndex = startIndex.value + index;
        itemHeights.value[realIndex] = item.offsetHeight;
    });
};

// 初始化时触发一次滚动事件
onMounted(() => {
    visibleCount.value = Math.ceil(
        container.value.clientHeight / props.itemHeight
    );
    handleScroll();
});
// 关键暴露步骤
defineExpose({
    freshVirtualList: () => {
        nextTick(() => {
            updateItemHeights();
        })
    }
})
// 更新时重新计算高度
onUpdated(() => {
    updateItemHeights();
});
</script>

<template>
    <div class="virtual-list-container" @scroll="handleScroll" ref="container">
        <div class="virtual-list-wrapper" :style="{ height: totalHeight + 'px' }">
            <div class="virtual-list-item" v-for="item in visibleItems" :key="item.id"
                :style="{ transform: `translateY(${item.offset}px)` }" ref="items">
                <slot :item="item"></slot>
            </div>
        </div>
    </div>
</template>

<style scoped lang="scss">
.virtual-list-container {
    height: 100%;
    /* 容器高度 */
    overflow-y: auto;
    /* 允许垂直滚动 */
}

.virtual-list-wrapper {
    position: relative;
}

.virtual-list-item {
    position: absolute;
    width: 100%;
    box-sizing: border-box;
}
</style>
